#pragma once

#include "TcpServer.hpp"
#include "Socket.hpp"
#include "Log.hpp"
#include "Util.hpp"

#include <memory>
#include <sstream>
#include <string>

using namespace SocketModule;
using namespace logModule;

const std::string gspace = " ";
const std::string glinespace = "\r\n";
const std::string glinesep = ": ";

const std::string webroot = "./wwwroot";
const std::string homepage = "index.html";
const std::string page_404 = "404.html";

class HttpRequest
{

public:

    HttpRequest()
        :_isInteract(false)
    {
        ;
    }

    ~HttpRequest()
    {
        ;
    }

    const std::string& getMethod() const
    {
        return _method;
    }

    void parseReqLine(std::string& reqLine)
    {
        std::stringstream ss(reqLine);
        ss >> _method >> _url >> _version;
    }

    std::string serialize()
    {
        std::string ans;
        return ans;
    }

    bool deserialize(std::string& str)
    {
        std::string reqLine;
        bool ret = Util::readOneLine(str, reqLine, glinesep);
        parseReqLine(reqLine);
        
        LOG(LogLevel::DEBUG) << reqLine;
        LOG(LogLevel::DEBUG) << "_method:" << _method;
        LOG(LogLevel::DEBUG) << "_url: " << _url;
        LOG(LogLevel::DEBUG) << "_version: " << _version;

        if (_url == "/")
        {
            _url = webroot + _url + homepage;
        }
        else
        {
            _url = webroot + _url;
        }

        if (_method == "post" || _method == "POST")
        {
            _isInteract = true;
            auto pos = str.find("text=");
            if (pos != std::string::npos)
            {
                _text = str.substr(pos);
            }
            else
            {
                LOG(LogLevel::WARNING) << "string find failure, is npos";
            }
            return true;
        }
        
        const std::string temp = "?";
        auto pos = _url.find(temp);
        if (pos == std::string::npos)
        {
            return true;
        }


        
        _args = _url.substr(pos + temp.size());
        _url = _url.substr(0, pos);
        _isInteract = true;

        return true;
    }

    std::string getURL() const
    {
        return _url;
    }

    void setIsInteract(bool isInteract)
    {
        _isInteract = isInteract;
    }

    bool getIsInteract()
    {
        return _isInteract;
    }

    std::string getArgs() 
    {
        return _args; 
    }

    const std::string& getText() const
    {
        return _text;
    }

private:

    std::string _method;
    std::string _url;
    std::string _version;

    std::unordered_map<std::string, std::string> _headers;
    std::string _blankLine;
    std::string _text;

    std::string _args;
    
    bool _isInteract;
};

class HttpResponse
{
public:

    HttpResponse()
        :_blankLine(glinespace)
        ,_version("HTTP/1.0")
    {
        ;
    }
    
    ~HttpResponse()
    {
        ;
    }

    std::string serialize()
    {
        std::string status_line = _version + gspace + std::to_string(_code) + gspace + _desc + glinespace;
        std::string resp_header;
        for (auto &header : _headers)
        {
            std::string line = header.first + glinesep + header.second + glinespace;
            resp_header += line;
        }

        return status_line + resp_header + _blankLine + _text;
    }

    void setCode(int code)
    {
        _code = code;
        switch(code)
        {
        case 200:
            _desc = "OK";
            break;
        case 404:
            _desc = "Not Found";
            break;
        case 301:
            _desc = "Moved Permanently";
            break;
        case 302:
            _desc = "See Other";
            break;
        default:
            break;
        }
    }

    void setHeader(const std::string& key, const std::string& value)
    {
        auto it = _headers.find(key);
        if (it == _headers.end())
        {
            _headers.insert({key, value});
        }
    }

    std::string URLSuffix(const std::string& targetFile)
    {
        auto pos = targetFile.find(".");
        if (pos == std::string::npos)
        {
            return "text/html; charset=utf-8";
        }

        std::string suffix = targetFile.substr(pos);
        if (suffix == ".html" || suffix == ".htm")
        {
            return "text/html; charset=utf-8";
        }
        else if (suffix == ".jpg")
        {
            return "image/jpg";
        }
        else if (suffix == ".png")
        {
            return "image/png";
        }
        else
        {
            return "";
        }
    }

    bool deserialize(const std::string& str)
    {

        return true;
    }

    void setTargetFile(const std::string& target)
    {
        _targetFile = target;
    }

    bool makeResponse()
    {
        if (_targetFile == "./wwwroot/favicon.ico")
        {
            LOG(LogLevel::DEBUG) << "用户请求: " << _targetFile << " 忽略它";
            return false;
        }
        if (_targetFile == "./wwwroot/getSame")
        {
            setCode(302);
            setHeader("Location", "https://www.qq.com/");
            return true;
        }
        
        int fileSize = 0;
        std::string suffix;
        bool res = Util::readFileContent(_targetFile, _text);
        if (res == false)
        {
            _text = "";
            LOG(LogLevel::DEBUG) << "client want get: " << _targetFile << " but no found";
            setCode(404);
            _targetFile = webroot + "/" + page_404;
            fileSize = Util::fileSize(_targetFile);
            Util::readFileContent(_targetFile, _text);
            suffix = URLSuffix(_targetFile);
        }
        else
        {
            fileSize = Util::fileSize(_targetFile);
            LOG(LogLevel::DEBUG) << "读取文件: " << _targetFile;
            setCode(200);
            suffix = URLSuffix(_targetFile);
        }
        setHeader("Content-Type", suffix);
        setHeader("Content-Lenth", std::to_string(fileSize));

        return true;
    }

    void setText(const std::string &t)
    {
        _text = t;
    }

// private:

    std::string _version;
    int _code;
    std::string _desc;

    std::unordered_map<std::string, std::string> _headers;
    std::string _blankLine;
    std::string _text;

    std::string _targetFile;
};

using http_func_t = std::function<void(HttpRequest &req, HttpResponse &resp)>;

class HttpProtocol
{
public:

    HttpProtocol(uint16_t port)
        :_server(std::make_unique<TcpServer>(port))
    {
        ;
    }

    void handlerHttpRequest(std::shared_ptr<Socket> sock, const InetAddr& client)
    {
        std::string req_buff;
        int n = sock->recv(req_buff);
        if (n > 0)
        {
            // 对报文完整性进行审核 -- 缺
            HttpRequest req;
            req.deserialize(req_buff);
            HttpResponse resp;
            // resp._version = "HTTP/1.1";
            // resp._code = 200;
            // resp.setTargetFile(req.getURL());
            // resp.makeResponse();

            if (req.getIsInteract())
            {
                // _uri: ./wwwroot/login
                if (_route.find(req.getURL()) == _route.end())
                {
                    // SetCode(302)
                }
                else
                {
                    LOG(LogLevel::DEBUG) << "进入动态交互";
                    _route[req.getURL()](req, resp);
                    std::string response_str = resp.serialize();
                    sock->send(response_str);
                }
            }
            else
            {
                resp.setTargetFile(req.getURL());
                if (resp.makeResponse())
                {
                    std::string response_str = resp.serialize();
                    sock->send(response_str);
                }
            }

            std::string response_str = resp.serialize();
            sock->send(response_str);
            std::cout << "------------ request ------------" << std::endl;
            std::cout << req_buff;
            std::cout << "------------ request ------------" << std::endl;
            std::cout << "------------ response ------------" << std::endl;
            std::cout << response_str;
            std::cout << "------------ response ------------" << std::endl;
        }
    }

    void start()
    {
        _server->start([this](std::shared_ptr<Socket> sock, const InetAddr& client)
        {
            this->handlerHttpRequest(sock, client);
        });
    }

    void registerService(const std::string name, http_func_t h)
    {
        std::string key = webroot + name; // ./wwwroot/login
        auto iter = _route.find(key);
        if (iter == _route.end())
        {
            _route.insert(std::make_pair(key, h));
        }
    }

private:

    std::unique_ptr<TcpServer> _server;
    std::unordered_map<std::string, http_func_t> _route;
};